// Package main
// desc: x
// author: Locker
// date: 2024/8/22
// version: 1.0.0
package main

import (
	"crypto/tls"
	"errors"
	"fmt"
	"github.com/dustin/go-humanize"
	"github.com/spf13/cast"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync/atomic"
	"time"
)

const userAgent = `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36`

// perLength 每次下载 1MB
const perLength = 1024 * 1000

func Resume(finalPath, downUrl string, count *ReaderCount) error {
	var (
		target        *os.File
		err           error
		start         int = 0
		end           int
		fileTotalSize int
	)
	tmpFile := fmt.Sprintf("%s.tmp", finalPath)
	if runtime.GOOS == "windows" {
		tmpFile = strings.ReplaceAll(tmpFile, "/", "\\")
	}
	os.MkdirAll(filepath.Dir(tmpFile), os.ModePerm)

BeginDown:
	if FSIsExist(tmpFile) {
		// 如果存在，继续下载
		target, err = os.OpenFile(tmpFile, os.O_WRONLY|os.O_APPEND, 0666)
	} else {
		// 创建临时文件
		target, err = os.Create(tmpFile)
	}
	if err != nil {
		return err
	}
	down := func(start, end int, first bool) (int, error) {

		fmt.Printf("下载 范围[%s~%s] 占比[%s]\n",
			humanize.Bytes(uint64(start)), humanize.Bytes(uint64(end)), count.GetRate(),
		)

		req, err := http.NewRequest(http.MethodGet, downUrl, nil)
		if err != nil {
			return 0, err
		}
		req.Header.Set("User-Agent", userAgent)

		if !first {
			req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", start, end))
		}

		// 忽略 https 证书
		client := &http.Client{
			Transport: &http.Transport{
				TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
			},
		}
		resp, err := client.Do(req)
		if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
			return 0, errors.New("下载失败：http status" + resp.Status)
		}
		defer resp.Body.Close()

		fileSize := cast.ToInt(resp.Header.Get("Content-Length"))
		if first {
			return fileSize, nil
		}
		//var total uint64
		//total = cast.ToUint64(start)
		//count.SetFileSize(fileSize)
		//count.setTotal(&total)
		count.setReader(resp.Body)
		// 将下载的文件流拷贝到临时文件
		if _, err = io.Copy(target, count); err != nil {
			target.Close()
			return 0, err
		}
		return fileSize, nil
	}

	fileTotalSize, err = down(0, 1, true)
	if err != nil {
		return err
	}

	times := fileTotalSize/1000 + 1
	stat, err := target.Stat()
	if err != nil {
		target.Close()
		return err
	}

	// 查看文件的创建时间，如果创建时间超过2小时，删除重新下载
	lastModifyTime := stat.ModTime()
	if time.Now().Sub(lastModifyTime) > 2*time.Hour {
		// 如果修改时间超过两小时，删除重新下载
		target.Close()
		os.Remove(tmpFile)
		goto BeginDown
	}

	start = cast.ToInt(stat.Size())
	// 获取进度-设置参数
	var total uint64
	total = cast.ToUint64(start)
	count.SetFileSize(fileTotalSize)
	count.setTotal(&total)

	for t := 0; t < times; t++ {
		begin := t * perLength
		end = begin + perLength - 1
		if end > fileTotalSize {
			end = fileTotalSize
		}

		if end < start {
			continue
		}
		if begin < start && end >= start {
			begin = start
		}

		if begin >= end {
			continue
		}
		_, err = down(begin, end, false)
		if err != nil {
			return err
		}
	}

	// 查看最后文件大小
	info, err := target.Stat()
	if err != nil {
		target.Close()
		return err
	}
	// 关闭临时并修改临时文件为最终文件
	target.Close()
	if info.Size() == cast.ToInt64(fileTotalSize) {
		err = os.Rename(tmpFile, finalPath)
	}
	return err
}

type ReaderCount struct {
	FileSize  int
	Total     *uint64
	reader    io.ReadCloser
	over      bool
	err       error
	unzipOver bool
}

func (r *ReaderCount) Read(p []byte) (n int, err error) {
	read, err := r.reader.Read(p)
	if err == io.EOF {
		r.over = true
	}
	atomic.AddUint64(r.Total, cast.ToUint64(read))
	return read, err
}

func (r *ReaderCount) Close() error {
	return r.reader.Close()
}

func (r *ReaderCount) SetFileSize(filesize int) {
	r.FileSize = filesize
}
func (r *ReaderCount) setReader(reader io.ReadCloser) {
	r.reader = reader
}
func (r *ReaderCount) setTotal(total *uint64) {
	r.Total = total
}
func (r *ReaderCount) GetRate() string {
	if r.FileSize == 0 || r.Total == nil {
		return "0.0"
	}
	return fmt.Sprintf("%0.2f%%", cast.ToFloat64(r.Total)/cast.ToFloat64(r.FileSize)*100)
}
func (r *ReaderCount) IsOver() bool {
	return r.over
}
func (r *ReaderCount) SetErr(err error) {
	r.err = err
}

func (r *ReaderCount) GetErr() error {
	return r.err
}
func (r *ReaderCount) SetUnzipOver(b bool) {
	r.unzipOver = b
}
func (r *ReaderCount) GetUnzipOver() bool {
	return r.unzipOver
}

func NewReader() *ReaderCount {
	return &ReaderCount{}
}

func FSIsExist(path string) bool {
	// os.Stat获取文件信息
	_, err := os.Stat(path)
	if err != nil {
		return os.IsExist(err)
	}
	return true
}
